﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Linq.Dynamic.Core;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using PMS.SANY.Entity;
using PMS.SANY.IBusiness;
using PMS.SANY.Util;
using PMS.SANY.Models;
using LinqKit;

namespace PMS.SANY.Business
{
    public class CF_CodeTypeBusiness : BusRepository<CF_CodeType>, ICF_CodeTypeBusiness, IScopedDependency
    {
        private IServiceProvider SvcProvider { get; set; }
        public CF_CodeTypeBusiness(GDbContext context, IServiceProvider svcProvider)
            : base(context)
        {
            this.SvcProvider = svcProvider;
        }

        public Task<PageResult<CF_CodeType>> GetPageResultAsync(PageInput<CF_CodeTypeQM> query)
        {
            var queryable = this.GetQueryable(true);
            var search = query.Search;            
            if (!search.Keyword.IsNullOrEmpty())
                queryable = queryable.Where(w => w.Name.Contains(search.Keyword) || w.Code.Contains(search.Keyword));

            return this.GetPageResultAsync(queryable, query);
        }

        public async Task<string> GenerateAsync(string whseId, string code, Dictionary<string, string> para = null)
        {
            var codeType = await this.GetQueryable(true)
                .Include(w => w.CodeRules)
                .Where(w => w.Code == code)
                .SingleAsync();
            var listCode = new List<string>();
            var rules = codeType.CodeRules.OrderBy(o => o.Sort);

            var sysParaName = new List<string>() { "Serial", "PerDay", "PerMonth", "PerYear" };
            var serialWhere = LinqHelper.True<CF_CodeSerial>();
            serialWhere = serialWhere.And(w => w.CompanyId == whseId && w.CodeTypeId == codeType.Id && sysParaName.Contains(w.ParaName));
            foreach (var rule in rules.Where(w => w.RuleType == "ParaSerial"))
            {
                if (para.ContainsKey(rule.Rule))
                {
                    var paraVal = para[rule.Rule];
                    serialWhere = serialWhere.Or(w => w.CompanyId == whseId && w.CodeTypeId == codeType.Id && w.ParaName == rule.Rule && w.ParaValue == paraVal);
                }
            }
            var listSerial = await this.GetQueryable<CF_CodeSerial>().Where(serialWhere).ToListAsync();

            foreach (var rule in rules)
            {
                var ruleCode = this.GenerateByRule(listSerial, rule, para);
                if (!ruleCode.IsNullOrEmpty())
                    listCode.Add(ruleCode);
            }

            var serialSvc = this.SvcProvider.GetRequiredService<ICF_CodeSerialBusiness>();
            var listUpdate = listSerial.Where(w => string.IsNullOrEmpty(w.CodeTypeId) && !string.IsNullOrEmpty(w.Id)).ToList();
            if (listUpdate.Count > 0)
            {
                listUpdate.ForEach(item => { item.CompanyId = whseId; item.CodeTypeId = codeType.Id; });
                await serialSvc.UpdateAsync(listUpdate);
            }
            var listAdd = listSerial.Where(w => string.IsNullOrEmpty(w.CodeTypeId) && string.IsNullOrEmpty(w.Id)).ToList();
            if (listAdd.Count > 0)
            {
                var idSvc = this.SvcProvider.GetRequiredService<IIdService>();
                listAdd.ForEach(item =>
                {
                    item.CompanyId = whseId;
                    item.Id = idSvc.GetId();
                    item.CodeTypeId = codeType.Id;
                });
                await serialSvc.AddAsync(listAdd);
            }
            var resultCode = string.Join(codeType.JoinChar, listCode);
            return resultCode;
        }
        public static readonly char[] r = new char[] { 'Q', 'A', 'Z', 'W', 'S', 'X', '9', 'E', 'D', '1', 'C', 'R', '8', 'F', 'V', '2', 'T', 'G', '7', 'B', 'Y', '3', 'H', 'N', '6', 'U', 'J', '4', 'M', 'K', '5', 'L', 'P' };
        private string GenerateByRule(List<CF_CodeSerial> listSerial, CF_CodeRule rule, Dictionary<string, string> para = null)
        {
            var now = DateTime.Now;
            var code = "";
            switch (rule.RuleType)
            {
                case "Const": code = rule.Rule; break;
                case "Date": code = now.ToString(rule.Rule.IsNullOrEmpty() ? "YYYYMMDD" : rule.Rule); break;
                case "Serial":
                    {
                        var serial = listSerial.SingleOrDefault(w => w.ParaName == "Serial");
                        if (serial == null)
                        {
                            serial = new CF_CodeSerial()
                            {
                                ParaName = "Serial",
                                ParaValue = "",
                                SerialNum = 0
                            };
                            listSerial.Add(serial);
                        }
                        else serial.CodeTypeId = null;
                        var seq = serial.SerialNum + 1;
                        code = seq.ToString();
                        if (rule.Length > 0)
                            code = code.PadLeft(rule.Length, '0');
                        serial.SerialNum = seq;
                    }
                    break;
                case "PerDay":
                    {
                        var serial = listSerial.SingleOrDefault(w => w.ParaName == "PerDay");
                        if (serial == null)
                        {
                            serial = new CF_CodeSerial()
                            {
                                ParaName = "PerDay",
                                ParaValue = now.Date.ToString("yyyy-MM-dd HH:mm:ss"),
                                SerialNum = 0
                            };
                            listSerial.Add(serial);
                        }
                        else serial.CodeTypeId = null;
                        var seqDate = serial.ParaValue.ToDateTime();
                        if ((now - seqDate).Days == 0)
                        {
                            var seq = serial.SerialNum + 1;
                            code = seq.ToString();
                            if (rule.Length > 0)
                                code = code.PadLeft(rule.Length, '0');
                            serial.SerialNum = seq;
                        }
                        else
                        {
                            var seq = 1;
                            code = seq.ToString();
                            if (rule.Length > 0)
                                code = code.PadLeft(rule.Length, '0');
                            serial.SerialNum = seq;
                            serial.ParaValue = now.Date.ToString("yyyy-MM-dd HH:mm:ss");
                        }
                    }
                    break;
                case "PerMonth":
                    {
                        var serial = listSerial.SingleOrDefault(w => w.ParaName == "PerMonth");
                        if (serial == null)
                        {
                            serial = new CF_CodeSerial()
                            {
                                ParaName = "PerMonth",
                                ParaValue = now.Date.ToString("yyyy-MM-dd HH:mm:ss"),
                                SerialNum = 0
                            };
                            listSerial.Add(serial);
                        }
                        else serial.CodeTypeId = null;
                        var seqDate = serial.ParaValue.ToDateTime();
                        if (now.Year == seqDate.Year && now.Month == seqDate.Month)
                        {
                            var seq = serial.SerialNum + 1;
                            code = seq.ToString();
                            if (rule.Length > 0)
                                code = code.PadLeft(rule.Length, '0');
                            serial.SerialNum = seq;
                        }
                        else
                        {
                            var seq = 1;
                            code = seq.ToString();
                            if (rule.Length > 0)
                                code = code.PadLeft(rule.Length, '0');
                            serial.SerialNum = seq;
                            serial.ParaValue = now.Date.ToString("yyyy-MM-dd HH:mm:ss");
                        }
                    };
                    break;
                case "PerYear":
                    {
                        var serial = listSerial.SingleOrDefault(w => w.ParaName == "PerYear");
                        if (serial == null)
                        {
                            serial = new CF_CodeSerial()
                            {
                                ParaName = "PerYear",
                                ParaValue = now.Date.ToString("yyyy-MM-dd HH:mm:ss"),
                                SerialNum = 0
                            };
                            listSerial.Add(serial);
                        }
                        else serial.CodeTypeId = null;
                        var seqDate = serial.ParaValue.ToDateTime();
                        if (now.Year == seqDate.Year)
                        {
                            var seq = serial.SerialNum + 1;
                            code = seq.ToString();
                            if (rule.Length > 0)
                                code = code.PadLeft(rule.Length, '0');
                            serial.SerialNum = seq;
                        }
                        else
                        {
                            var seq = 1;
                            code = seq.ToString();
                            if (rule.Length > 0)
                                code = code.PadLeft(rule.Length, '0');
                            serial.SerialNum = seq;
                            serial.ParaValue = now.Date.ToString("yyyy-MM-dd HH:mm:ss");
                        }
                    };
                    break;
                case "Random":
                    {
                        var idSvc = this.SvcProvider.GetRequiredService<IIdService>();
                        var newId = idSvc.GetId().ToLong();
                        var buf = new char[32];
                        var binLen = 33;
                        int charPos = 32;
                        while ((newId / binLen) > 0)
                        {
                            int ind = (int)(newId % binLen);
                            buf[--charPos] = r[ind];
                            newId /= binLen;
                        }
                        buf[--charPos] = r[(int)(newId % binLen)];
                        code = new string(buf, charPos, (32 - charPos));
                        if (rule.Length > 0)
                            code = code.PadLeft(rule.Length, '0');
                    }
                    break;
                case "Parameter":
                    {
                        if (para.ContainsKey(rule.Rule))
                            code = para[rule.Rule];
                        else
                            code = "";
                        if (rule.Length > 0)
                            code = code.PadLeft(rule.Length, '0');
                    }
                    break;
                case "ParaSerial":
                    {
                        if (para.ContainsKey(rule.Rule))
                        {
                            var serial = listSerial.SingleOrDefault(w => w.ParaName == rule.Rule && w.ParaValue == para[rule.Rule]);
                            if (serial == null)
                            {
                                serial = new CF_CodeSerial()
                                {
                                    ParaName = rule.Rule,
                                    ParaValue = para[rule.Rule],
                                    SerialNum = 0
                                };
                                listSerial.Add(serial);
                            }
                            else serial.CodeTypeId = null;
                            var seq = serial.SerialNum + 1;
                            code = seq.ToString();
                            if (rule.Length > 0)
                                code = code.PadLeft(rule.Length, '0');
                            serial.SerialNum = seq;
                        }
                    }
                    break;
                default: break;
            }
            return code;
        }
    }
}
